/*
 * Copyright (C) 2007  Vianney le Clément
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package fsahoraires.programmes;

import java.util.*;

import fsahoraires.cours.*;
import fsahoraires.cours.Horaire.Jour;

public class Semestre implements Iterable<Cours>, Comparable<Semestre> {

	private final Programme programme;
	private final int annee, semestre;

	private SortedSet<Cours> cours;

	private boolean disposed = false;

	private void checkDisposed() {
		if (disposed)
			throw new IllegalStateException("Semestre is disposed.");
	}

	public boolean isDisposed() {
		return disposed;
	}

	private final CoursManagerListener cmListener = new CoursManagerAdapter() {
		public void disposed(Cours c) {
			remove(c);
		}
	};

	private List<SemestreListener> listeners = new ArrayList<SemestreListener>();

	public void addSemestreListener(SemestreListener listener) {
		listeners.add(listener);
	}

	public void removeSemestreListener(SemestreListener listener) {
		listeners.remove(listener);
	}

	private void fireAdded(Cours cours) {
		for (SemestreListener listener : listeners)
			listener.added(this, cours);
	}

	private void fireRemoved(Cours cours) {
		for (SemestreListener listener : listeners)
			listener.removed(this, cours);
	}

	/**
	 * Crée un nouveau semestre
	 * 
	 * @param annee année (1, 2, 3, ...)
	 * @param semestre semestre (1 ou 2)
	 * @throws IllegalArgumentException si annee ou semestre n'est pas correct
	 * @throws NoSuchElementException si le semestre existe déjà dans le
	 *             programme
	 */
	public Semestre(Programme programme, int annee, int semestre)
			throws IllegalArgumentException, NoSuchElementException {
		if (programme == null || annee < 1 || semestre < 1 || semestre > 2)
			throw new IllegalArgumentException("programme, annee " + annee
					+ " ou semestre " + semestre + " incorrect");
		this.programme = programme;
		this.annee = annee;
		this.semestre = semestre;
		cours = new TreeSet<Cours>();
		if (!programme.add(this))
			throw new NoSuchElementException(
					"Le semestre existe déjà dans le programme.");
		programme.getManager().addCoursManagerListener(cmListener);
	}

	/**
	 * Crée un semestre basé sur un autre
	 */
	public Semestre(Programme programme, Semestre other) {
		if (programme == null || other == null)
			throw new IllegalArgumentException(
					"programme et semestre ne peuvent pas être null");
		this.programme = programme;
		this.annee = other.getAnnee();
		this.semestre = other.getSemestre();
		cours = new TreeSet<Cours>();
		if (!programme.add(this))
			throw new NoSuchElementException(
					"Le semestre existe déjà dans le programme.");
		CoursManager manager = programme.getManager();
		manager.addCoursManagerListener(cmListener);
		for (Cours c : other.getCours())
			add(manager.getCours(c));
	}

	public int getAnnee() {
		return annee;
	}

	public int getSemestre() {
		return semestre;
	}

	public Programme getProgramme() {
		return programme;
	}

	public SortedSet<Cours> getCours() {
		return Collections.unmodifiableSortedSet(cours);
	}

	public Horaire getHoraire() {
		checkDisposed();
		CompoundHoraire horaire = new CompoundHoraire();
		for (Cours cours : this)
			horaire.add(cours.getHoraire(semestre));
		return horaire;
	}

	public Horaire getHoraire(int semaine) {
		checkDisposed();
		CompoundHoraire horaire = new CompoundHoraire();
		for (Cours cours : this) {
			Horaire h = cours.getHoraire(semestre);
			for (Jour j : Jour.values())
				for (int t = 0; t < Horaire.TRANCHES; t++)
					for (Occupation o : h.getOccupations(j, t))
						if (o.getSemaines().get(semaine))
							horaire.add(o, j, t);
		}
		return horaire;
	}

	public float getCredits() {
		checkDisposed();
		float credits = 0;
		for (Cours cours : this)
			credits += (cours.getCredits() / programme.getSemestresCount(cours));
		return credits;
	}

	public float getHeures() {
		checkDisposed();
		float heures = 0;
		for (Cours cours : this)
			heures += (cours.getHeures() / programme.getSemestresCount(cours));
		return heures;
	}

	public int getHeuresParSemaine() {
		checkDisposed();
		int result = 0;
		for (Cours cours : this)
			result += cours.getHoraire(semestre).getHeuresParSemaine();
		return result;
	}

	/**
	 * Ajoute un cours à ce semestre
	 * 
	 * @param cours
	 * @return true si le cours a été ajouté, false si le cours appartenait déjà
	 *         au semestre
	 */
	public boolean add(Cours cours) {
		checkDisposed();
		if (!this.cours.contains(cours)) {
			this.cours.add(cours);
			fireAdded(cours);
			return true;
		} else {
			return false;
		}
	}

	public boolean remove(Cours cours) {
		checkDisposed();
		if (this.cours.remove(cours)) {
			fireRemoved(cours);
			return true;
		} else {
			return false;
		}
	}

	public void dispose() {
		programme.getManager().removeCoursManagerListener(cmListener);
		programme.remove(this);
		disposed = true;
	}

	public Iterator<Cours> iterator() {
		checkDisposed();
		return cours.iterator();
	}

	public String toString() {
		checkDisposed();
		StringBuilder str = new StringBuilder("Année ");
		str.append(annee).append(" quadrimestre ").append(semestre);
		return str.toString();
	}

	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (!(obj instanceof Semestre))
			return false;
		Semestre sem = (Semestre) obj;
		return (annee == sem.annee && semestre == sem.semestre);
	}

	public int compareTo(Semestre o) {
		if (annee == o.annee)
			return semestre - o.semestre;
		else
			return annee - o.annee;
	}

}
